package com.xiaobias.a21;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class A21 {

    public static void main(String[] args) throws Throwable {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        // 准备测试 Request
        HttpServletRequest request = mockRequest();

        // 要点一. 控制器方法被封装为 HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        // 要点二. 准备对象绑定与类型转换
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        // 要点三. 准备 ModelAndViewContainer 用来存贮中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 要点四. 解析每一个参数值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {


            // 多个解析器组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    // false 表示必须有 @RequestParam
                    new RequestParamMethodArgumentResolver(beanFactory, false),
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false),
                    new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                    new ServletModelAttributeMethodProcessor(true),
                    new RequestParamMethodArgumentResolver(beanFactory, true)
            );

            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(annotation -> annotation.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = !annotations.isEmpty() ? " @" + annotations + " " : " ";
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (composite.supportsParameter(parameter)) {
                // 支持此参数
                Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
                //System.out.println(v.getClass());
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + " -> " + v);
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + " ");
            }

        }
    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "张三");
        request.setParameter("age", "18");
        request.setContent("""
                {
                    "name" : "李四",
                    "age": 20
                }
                """.getBytes(StandardCharsets.UTF_8));

        return new StandardServletMultipartResolver().resolveMultipart(request);
    }



/*
    目标： 解析控制器方法的参数值

    常见的参数处理器如下：
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@66e8997c
        org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@6f0cb5a1
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@1b29d52b
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@47547132
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@655523dd
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@6f7e336b
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@3a2b2322
        org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5e1218b4
        org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@12958360
        org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@c6e0f32
        org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6f3f0fae
        org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@21a66d45
        org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@1f966492
        org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@63fdffcd
        org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@7428de63
        org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@66f659e6
        org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@4a68135e
        org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@46678e49
        org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@748e9b20
        org.springframework.web.method.annotation.ModelMethodProcessor@2063c53e
        org.springframework.web.method.annotation.MapMethodProcessor@2a9bc08f
        org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@50f40653
        org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@50a3d0f6
        org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@6e57b5e9
        org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@6a0ac48e
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@24e08d59
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@77cb9cd1
 */

    static class Controller {
        public void test(
                @RequestParam("name1") String name1,            // name1=张三
                String name2,                                   // name2=李四
                @RequestParam("age") int age,                   // age=18    会涉及到类型转换
                @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // Spring 获取数据
                @RequestParam("file") MultipartFile file,       // 获取上传文件
                @PathVariable("id") int id,                     // /test/123     /test/{id}
                @RequestHeader("Content-Type") String header,
                @CookieValue("token") String token,
                @Value("${JAVA_HOME}") String home2,             // Spring 获取数据
                HttpServletRequest request,
                @ModelAttribute("adc") User user1,               // name=zhang&age=18
                User user2,                                      // name=zhang&age=18
                @RequestBody User user3                          // json
        ) {
        }
    }
}

class User implements Serializable {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

